test(web): work toward 100% mutation coverage - #1255
Draft
Mearman wants to merge 72 commits into
Draft
Conversation
Mearman
force-pushed
the
feat/100-percent-mutation-web
branch
from
September 13, 2026 14:29
a410718 to
1eaa4cf
Compare
|
|
||
| describe("ResizeObserver stub", () => { | ||
| it("constructs and exposes observe/unobserve/disconnect as callable no-ops", () => { | ||
| const observer = new ResizeObserver(() => {}); |
….ts and workers/**
tsconfig.json (the main app program) deliberately excludes src/rpc/router.ts and every
src/workers/**/*.ts file, since they belong to tsconfig.worker.json's own DOM-vs-WebWorker lib
split instead. Stryker's typescript-checker plugin requires every mutated file to belong to the
one program its tsconfigFile resolves, so pointing it at tsconfig.json crashed outright the
moment a mutant landed inside router.ts or the worker entry point ("no watcher is registered for
it"). tsconfig.stryker.json is a checker-only program: the same include as tsconfig.json but
without the router.ts/workers exclusions, with DOM and WebWorker unioned (skipLibCheck makes the
pair compile together) so both halves of the app typecheck under the one program the checker
needs.
inferFormatFromFilename and relativeTime had no unit coverage at all despite being pure, easily-tested functions -- every extension/alias mapping, the lowercase-before-match step, the dotfile and no-extension edge cases, and each relativeTime unit boundary (minute/hour/day, floored not rounded) are now exercised directly.
…icker's accept extension
String.split('.').pop() can never return undefined for any input, including a string with no '.'
at all -- split always returns at least one element -- so the '?? "bin"' fallback in
createNativeFileAccess's saveFile was dead code with no test able to reach it. Removed the guard
and added full unit coverage for all three file-access adapters (createFileAccess's native/
fallback selection, the fallback picker's file-chosen/dismissed/accept-attribute paths and its
Blob-URL download-anchor save, and the native picker's open/save flows including the
AbortError-vs-real-failure branches and the accept-extension derivation this fix touches).
Neither src/db/dexie.ts nor src/hooks/useRecentFiles.ts had any unit coverage -- jsdom implements no IndexedDB of its own, so nothing could construct the Dexie instance at module load without one. Adds fake-indexeddb (installed globally in the unit project's test setup, ahead of any test's own import of the db module) and exercises the database's own table schema plus recordRecentFile's 20-entry FIFO eviction and removeRecentFile.
…nt converter None of src/hooks/**'s useMutation/useQuery wrappers around getRpcClient(), nor workerDocumentConverter's own convertViaWorker, had any unit coverage. Adds a small, dependency-free render harness (mounting a hook inside a real jsdom tree via react-dom/client and a fresh QueryClientProvider, the same approach src/ui/contentBlocks.test.tsx already established for component-level tests) and a fully-typed mock RPC client fixture (one vi.fn() per router procedure), then uses both to exercise useConversions, useDocumentFormats, useReadMetadata, useWriteMetadata, useExtractSourceFonts, useReadContent, useRestoreContent, useReadOdb, useOdmRender, useConvert, the five useEditorSession mutations, usePdfObjectUrl's blob-URL lifecycle, and convertViaWorker's own field-narrowing of its RPC call.
contentInspectResult, useReadContent, useInspectPdfBytes, and useInspectDocument (src/hooks/ useInspect.ts) had no coverage -- exercises the pure content-backed result builder, the content.read/pdf.inspect RPC calls, and useInspectDocument's own branch between inspecting PDF bytes directly versus converting a non-PDF source to PDF first and carrying the conversion's own diagnostics through.
router.test.ts already covered normalizeContentForSource and the editor-session helper functions directly, but none of router.ts's actual exported procedures (formats.list/listConversions, convert, content.read/restore, metadata.read/write, fonts.extractSourceFonts, pdf.inspect, and the full editor.open/setParagraphText/addParagraph/removeParagraph/save lifecycle including its unknown-session-id error path) had ever been called through oRPC's own dispatch. Uses @orpc/ server's call() to invoke each procedure directly against real markdown/docx fixtures. Forced onto vitest's node environment: jsdom's own TextEncoder constructs its Uint8Array in a different realm than the bare Uint8Array a z.instanceof(Uint8Array) input schema checks against under jsdom, which otherwise rejects every real byte payload as "expected Uint8Array, received Uint8Array".
Adds a Mantine-aware mount harness (mountWithMantine) and a real DiagnosticsPanel test suite, asserting on the Spoiler wrapper's own class marker rather than its "Show N more" label text: jsdom has no layout engine, so Spoiler's internal measured-height-vs-maxHeight comparison can never observe a real overflow and the label never renders regardless of item count. Stubs window.matchMedia and ResizeObserver in the shared jsdom test setup, guarded on `typeof window` since router.procedures.test.ts forces a node environment for the same file. Both APIs are called unconditionally by MantineProvider/Spoiler on mount, so any test that mounts a Mantine component needs them regardless of what it actually exercises. Restates vitest.mutation.config.ts's own setupFiles key, dropped by the same object-literal override that already restates environment: "jsdom", since fake-indexeddb needs to install before dexie.ts's module-scope Dexie construction runs.
main.tsx called its own root-mounting logic unconditionally at module scope, so the only way to exercise the missing-#root failure path was to import main.tsx itself -- which immediately mounts the real App against whatever #root element the test environment's own document happens to have. Moving the logic into mountApp.tsx, parameterised on the target Document, lets mountApp.test.tsx drive both branches directly against a throwaway jsdom Document, with createRoot mocked so the real router/worker stack is never pulled in. tsconfig.node.json's own program never included src/vite-env.d.ts, so the ambient __APP_COMMIT_SHA__ family of build-time globals were invisible whenever a test transitively imported far enough into the app (App -> router -> routeTree.gen -> every route, including -Sidebar.tsx, which reads them) to pull those files into that program. mountApp.test.tsx's own import chain is the first test to reach that deep, surfacing the gap.
Adds direct coverage for the three cases contentBlocks.test.tsx never exercised: an <annotation> element skipped along with its children, a cdata/comment/declaration/pi node producing no displayable content at all, and interleaved text/skip siblings rendering in order. Removes the redundant containerRef.current null check in MathMlView's effect: the ref is attached to an unconditionally rendered element of the same component instance, and React attaches refs during commit, strictly before a passive effect can observe them, so the guard could never genuinely take its true branch.
…ror's Error/non-Error split notifySuccess picks colour, title suffix, message, and autoClose entirely off whether any diagnostic is warning-severity and how many there are; notifyError reads .message off a real Error but stringifies anything else thrown. Neither had a test before this.
…ontract Asserts the empty-mailbox default, a plain set-then-take round trip, that a take clears the entry so a second take sees nothing, and that a later set overwrites an earlier entry nobody ever took.
Asserts no Tree root renders for a value with no browsable children (an empty object, or a primitive), and that one does once the value has at least one array/object entry to browse.
… SheetPreview Adds a shared src/test/fixtures.ts (a real DocumentTreeJson and a page size, built once outside src/ui/** so no UI test needs to import documents.js's conversion functions directly and trip the package's own import-boundary lint rule). InspectPanel: loading/error/empty branches, content-backed summary + structure tree, pdf-backed page count (singular vs plural), item-kind table, and conditional title/producer lines. FormulaPreview / WordProcessingPreview: the shared loading/error/ no-content/wrong-kind-of-document branches every format-specific preview repeats, plus each one's own real rendering path (MathML for a formula document, section blocks for a wordprocessing one). SheetPreview: single-vs-multiple-sheet SegmentedControl visibility, hidden row/column filtering, index-based ordering independent of array position, the empty-sheet fallback for no visible rows/columns, and a cell's own displayText rendering.
…ering Covers the presentation-vs-drawing content split (slides vs pages), single-vs-multiple-slide SegmentedControl visibility, every vector kind (rect, ellipse, line, path with line/cubic segments and open vs closed subpaths), solid/dashed/dotted/double stroke rendering (the double case simulated as a thick underlay plus a thin gap overlay, gap colour falling back to white when the shape has no fill), rotation transforms, paintOrder-driven ordering with an unset order sorting last, and a shape's own fontScale/lineSpacingReduction CSS derivation.
…malisation Mocks @mantine/dropzone's own Dropzone with a plain button exposing onDrop/onClick directly, since FileUpload's own logic (reading a dropped file's bytes, recording it when its extension resolves to a known format, opening the native picker when supported, normalising a single accept extension string into the array Dropzone expects) is what this package's mutate glob covers -- not the third-party drag-and-drop machinery Dropzone itself provides. Covers: file-present vs empty state (icon, name, hint visibility), loading/disabled passthrough, accept normalisation (string vs array, undefined), native-picker-driven onClick/activateOnClick wiring, a dropped file with no entries, and an unrecognised extension being handed to onFile without being recorded.
…ormatting Mocks useRecentFiles/removeRecentFile, useNavigate, notifyError, and setPendingReopen directly rather than exercising real IndexedDB and routing, since those are already covered by their own dedicated test suites -- this file's own logic is the permission-then-read-then- navigate chain, byte-size formatting thresholds, and the disabled/ unrecognised-format guards around it. Covers: the loading/empty/populated list states, B/KB/MB size formatting boundaries, the reopen action disabled with no handle, remove-by-id, a granted-on-first-query reopen, a granted-only-after request, a denied permission (notifies, never navigates), a read failure (notifies with the thrown error), and an unrecognised stored format (does nothing, silently).
Covers every recognised paragraph styleId (heading-1..6, quote, code-block, horizontal-rule, and the plain-paragraph fallback), image and table block delegation (the latter recursing back through this same markdown pipeline for cell content), and the list-grouping behaviour specific to this component: consecutive ordered/bullet runs collapse into one <ol>/<ul>, a type change between adjacent siblings splits into two lists, a deeper-level item nests inside its parent <li>, and a non-list paragraph interrupting a run starts a fresh list group afterward rather than merging with it.
Excludes *.test.ts(x) from the router plugin's route-tree scan (routeFileIgnorePattern) so a route's own unit test file doesn't itself get treated as an undeclared route -- the existing dash-prefix convention in this directory is for genuine non-route support files (-Sidebar.tsx), not a fit for a test file that belongs named like every other test in the package.
…e, testable lookups activeColorSchemeOption/nextColorSchemeOption/optionAt were inline RootLayout logic reachable only by mounting the full AppShell inside a real router and Mantine tree. Extracted as plain functions over a string value, __root.test.ts now drives every branch directly: optionAt's out-of-range throw (never reachable through RootLayout's own two call sites, since the modulo arithmetic guarantees a valid index, but a real invariant worth asserting explicitly rather than papering over with a silent fallback), an unrecognised current value falling back to the first option, and the wrap-around from the last option back to the first.
The router plugin's autoCodeSplitting rewrites every real route file's component behind a dynamic import, entirely independent of whether anything actually imports through the generated routeTree.gen.ts -- the transform keys off the route file's own path. A route-level unit test necessarily imports a route file directly (there is no other way to reach Route.options.component), so mounting it genuinely suspended waiting on a chunk vitest has no build pipeline reason to ever resolve quickly, and the very first such mount in a whole run could take several real seconds. Gated off under mode "test" the same way `base` above is already gated on `command`: a production bundle-size optimisation has no business affecting whether or how fast a test can render a route's component. Confirmed the real build still code-splits every route into its own chunk exactly as before.
…at guard Mocks the RPC client and FileUpload directly, exercising FontsPage's own composition: a recognised format triggers extractSourceFonts and renders each family with its bold/italic flags, an empty result shows the no-embedded-fonts message, an unrecognised extension neither calls extraction nor loses the alert, and a rejected extraction leaves no font table behind.
mountWithProviders wraps MantineProvider around a fresh QueryClientProvider per mount, for a route component that calls a react-query hook (useMutation/useQuery/useLiveQuery) itself rather than only through a hook this package already tests in isolation -- mirroring renderHookWithQueryClient's own per-mount QueryClient.
Add a full OdbPage test suite covering the pending/error/success states, the
inventory summary (connection type/url, table/query/form/report counts, the
queries list), and the SheetPreview handoff.
Drop the redundant readOdb.reset() call before mutate(): useMutation's own
"pending" dispatch already clears the previous data/error, so the extra call
never produced an observable state transition of its own. Drop the equally
redundant `inventory !== undefined` check alongside `data !== undefined` --
the read's output schema requires inventory whenever data is present, so the
second condition can never independently be false. Replace the useState("")
default (never actually rendered, since fileName is always set before the
value it seeds is read) with an unset default, extending the page's
database-label fallback to cover both "not yet picked" and "picked with an
empty name" explicitly.
Set stryker.config.ts's breakThreshold from this package's first measured
mutation baseline (40.52% of 1666 valid mutants), the gate every sibling
package already carries.
Add a full InspectPage test suite covering auto-detected inspection (both the direct PDF path and the convert-then-inspect path for other formats, including the conversion's own diagnostics carrying through), the unrecognised-format alert and manual format Select, and the notifyError path on a rejected inspection. Drop the redundant inspect.reset() call before mutate() -- useMutation's own "pending" dispatch already clears the previous data/error, so the extra call never produced an observable state transition of its own (same finding as odb.tsx). Drop the equally redundant `value === null` branch in handleFormatChange: DocumentFormatSchema.safeParse already rejects a null value the same way it rejects any other non-member string, so the explicit null check could never independently change the outcome.
Add a full OdmPage test suite covering a master-alone render, a chapter joining the set, a same-name chapter re-pick replacing rather than duplicating its entry while leaving other chapters untouched, the still-missing-chapters alert (including the empty-list case where it must stay hidden), the rendered PDF iframe's own attributes, and the notifyError path on a rejected render. Drop the redundant renderOdm.reset() call before mutate() -- useMutation's own "pending" dispatch already clears the previous data/error, so the extra call never produced an observable state transition of its own (same finding as odb.tsx and inspect.tsx).
…PdfObjectUrl produce useConversions/useDocumentFormats only asserted the eventually-resolved data, which react-query populates regardless of what key the query was registered under -- a mutated queryKey string or array still let the mock resolve and the assertions pass. Reading the same key back via queryClient.getQueryData pins the literal array these hooks pass to useQuery. usePdfObjectUrl's own Blob construction only had its .type checked, which a mutation dropping the wrapped bytes from the Blob array wouldn't affect; asserting .size against the source bytes' length does.
…nsfer wiring getRpcClient had no coverage at all: jsdom implements no Worker, so constructing one requires stubbing the global plus the two @orpc entry points its RPCLink wraps. The new suite stubs a fake Worker and RPCLink to assert the worker is constructed as a module-type worker pointed at the documents worker entry point, that the client is cached across calls (constructing the Worker only once), and that the RPCLink's experimental_transfer option returns the buffers cloneAndCollectTransferableBuffers collects when any exist and null otherwise.
…eir one real branch paragraphsOf's four format cases all called the identical session.editor.paragraphs(), so the switch existed purely as an artefact of the union type, not because any branch behaved differently -- collapsing it to a single unconditional call removes four case labels Stryker could flip to an identically-behaving fallthrough with no test able to tell the difference. appendParagraphOf's docx/odt/markdown cases were likewise identical (session.editor.body.appendParagraph), with doc the one genuine outlier (appendParagraph lives on the editor itself, not a body) -- narrowed to that one real branch instead of three redundant copies of the same call.
useRecentFiles() itself (the useLiveQuery wrapper -- ordering, 20-entry limit, and the effect deps array) had no test at all, converted from a plain .test.ts to .test.tsx to mount it. Extends the existing recordRecentFile/removeRecentFile coverage with the actual hook, asserting the returned list is ordered most-recent-first and capped at 20 entries even when more exist.
inferFormatFromFilename's leading-dot check only had ".gitignore" as
a dotfile example, whose suffix isn't a recognised extension either
way -- adding ".docx" (a hidden file, no real name) proves dotIndex
0 is rejected specifically, not just -1. stripMathMlNamespace's
colonIndex check needed a single-character prefix ("m:mfrac") to tell
"index === -1" apart from a mutation checking "index === 1": both the
existing no-colon and multi-character-prefix cases happen to slice or
pass through identically either way. cloneAndCollectTransferableBuffers
had no test where an array's own elements are Uint8Arrays directly
(only nested inside objects), leaving its own clone-and-push branch
untested. MathMlView had no test at all.
Only the wordprocessing and formula branches had any coverage; presentation (slide/shape counts) and drawing (page/shape/vector counts) had none, alongside a spreadsheet singular-count case (1 sheet, 1 cell) the existing plural-only example couldn't distinguish from an off-by-one mutation.
…requests
FormulaPreview/MarkdownPreview/WordProcessingPreview each hardcode a
previewFrame({scroll, padded}) variant object -- nothing previously
checked its actual argument, so a mutation flipping scroll or padded
to false, or dropping the object entirely, still rendered correctly
sized content in jsdom. Comparing the rendered className against the
recipe function called directly with the expected variants pins the
exact call each component makes. MarkdownPreview's renderRunsMd also
had no test for its distinguishing option: a run carrying fontFamily
renders as inline code only for markdown-sourced content.
The warnings/info split, both length>0 gates, and the collapse threshold boundary had no test proving the correct diagnostic lands in the correct list: a single warning plus a single info diagnostic now asserts exactly two list roots render (one each), and single-kind lists assert exactly one. The exact-threshold case (5 items) proves "more than" isn't "at least". Spoiler's showLabel text is unobservable under jsdom (it never measures a real overflow, so the label control never renders at all -- the existing SPOILER_WRAPPER_CLASS workaround already notes this) -- a dedicated file mocks just Spoiler to capture the label string DiagnosticsPanel actually passes it.
…y point Its whole body runs unconditionally at import (RPCHandler construction and handler.upgrade), so it had no coverage at all -- resetModules plus a fresh dynamic import per test forces that top-level code to genuinely re-run inside each test, letting Stryker attribute coverage to it correctly (verified directly via a scoped run: both this file and rpc/client.ts's own identical pattern reach 100%, 0 survived, 0 no-coverage). Mocks RPCHandler and the router to assert the handler is constructed with the real router and upgrades self with a context factory resolving to an empty object, and that experimental_transfer returns the collected buffers when any exist and null otherwise.
…tsconfig.node.json
A literal import("./documents.worker") specifier makes TypeScript
resolve and type-check that module inside every program that reaches
this test file, including tsconfig.node.json -- whose lib set has no
WebWorker, since its own test files run under jsdom rather than a real
worker. Under that lib set, self.postMessage type-checks against DOM's
Window overload instead of MessagePort's, which fails. The test never
needs the module's exported type (it has none), only the side effect
of importing it, so routing the specifier through a variable is enough
to stop TypeScript resolving the target module's types at all while
Vite still loads the real file at runtime exactly as before.
…lpers computeVersionInfo and computeVersionTooltip take the release tag, commit sha, and repo URL as parameters instead of reading __APP_RELEASE_TAG__ and its siblings inline, so both the tagged-release and bare-commit branches are directly testable regardless of the real git state a given checkout happens to build from.
Mounts the root route's component with the router, sidebar, and colour scheme hook stubbed, asserting the button's aria-label reflects the active and next colour-scheme options and that clicking it calls setColorScheme with the next option in the cycle.
colorSchemeTooltipLabel and navbarConfig take their inputs as plain parameters instead of being inlined into JSX props, so both are directly unit-testable: the tooltip's floating content only mounts into a portal once Floating UI's hover/focus interaction settles, which jsdom does not reliably drive, and the navbar's breakpoint/collapsed values otherwise surface only as generated CSS variables in Mantine's own stylesheet.
…ists Adds direct tests for renderRuns' bold/italic/underline/strike/hyperlink and inline-code wrapping, renderImage's data-URL construction, renderTable's per-cell callback dispatch, buildListForest's level-stack nesting and ordered/bullet detection, and collectBlockGroups' grouping and flush behaviour, plus heading/quote/code-block styleId dispatch, table and image rendering through renderBlocksNeutral, a construct-marker no-op render, and ordered/bullet/nested list grouping end to end.
…Blocks buildListForest's stack no longer seeds a synthetic root frame whose own level is compared against nothing (the length > 1 guard made it unreachable): an empty stack now means "attach to root" directly. renderParagraphContent stops defaulting a missing styleId to an empty string before matching the heading pattern, checking for undefined explicitly instead, since no fallback string changes the match outcome. renderBlockNeutral drops the early return for "no page-break flags" (the Fragment form already renders identically in that case) and renderPageBreak drops its unused key parameter, since neither call site sits inside an array. renderListNodesNeutral stops guarding the recursive call on children.length, since mapping an empty array is already a no-op.
Mearman
force-pushed
the
feat/100-percent-mutation-web
branch
from
September 13, 2026 22:28
823fad8 to
fd89f15
Compare
autoCodeSplitting was already gated off under vitest, but the router plugin itself still ran its route-tree codegen against every matched route file. That codegen requires each createFileRoute() call's own id argument to already be a plain string or template literal so it can rewrite it in place -- a requirement Stryker's own instrumentation breaks, since it rewrites every mutable literal (route ids included) into a stryMutAct-guarded conditional. Mutating more than one route file in the same run made the plugin's codegen throw across every route file at once, crashing the whole child process rather than marking a single mutant erroneous. No test imports routeTree.gen.ts (already committed, not regenerated per run) or mounts a route through the real router -- every route test mounts Route.options.component directly against the runtime factories @tanstack/react-router ships on its own, needing no Vite plugin at all. Confirmed with a full unit-test run (493/493 passing) after the change.
Neither router procedure had any test at all -- odb.read's inventory plus table-to-spreadsheet pipeline and odm.render's success and unresolved-section branches were exercised only by mocked UI tests that never call the real worker-side handler. Both procedures now run against genuine bytes built with documents.js's own public zipPackage/createOdt surface: a minimal embedded-HSQLDB .odb (mimetype, manifest, content.xml, and a real HSQLDB TEXT-format database/script) and a minimal .odm master referencing real .odt chapters, so no new dependency on odf.js is needed.
… remaining format branches normalizeContentForSource's heading detection, the Code/Source/Preformatted code-block heuristic, and the Horizontal+Line rule heuristic each had gaps that let the underlying boolean logic mutate without any test noticing: existing fixtures always satisfied every disjunct at once, so an OR could mutate to AND (or one disjunct forced constant) with no observable change. Added isolated single-disjunct fixtures for each, plus the two missing border-edge combinations (top+bottom, right+bottom) and a two-run whitespace paragraph proving the border check joins every run rather than reading only the first. Added direct tests distinguishing normalizeContentForSource's per-source dispatch branches from one another (a markdown-only styleId constant that the docx/odt heuristics don't happen to also match, and a call with deliberately invalid bytes proving the odt branch never touches its bytes argument), and table-cell recursion coverage for both the wordprocessing and markdown normalization paths. Added content.read coverage for every remaining source format (pptx/xlsx/odp/ods/odg) and the PDF standalone-reader rejection, an editor.* session test for the doc format (whose appendParagraph lives on the editor itself rather than a body), a genuinely multi-run paragraph collapse test, a zero-run paragraph append test, and a setParagraphText out-of-range test. Exported sanitizeImageAsset so its byteLength estimate can be pinned against a base64 string of a known length directly, and added a real docx-with-an-embedded-image fixture (built through the tree pipeline, since the docx editor exposes no image-insertion API) to prove pdf.inspect's per-page, per-item-kind tally and per-image sanitization both touch every item rather than reporting a stubbed empty result.
…ort forwarding
SheetPreview and SlidesPreview both clamp their active sheet/slide index
via Math.min(activeIndex, length - 1), but every existing test only ever
rendered once at the default index 0, where that clamp is unreachable --
mutating min to max, or the subtraction to addition, changed nothing
observable. Added tests that click the SegmentedControl to select a
later sheet/slide (exercising the onChange handler for the first time)
and then rerender with fewer items, proving the index actually clamps
back down rather than pointing past the end.
Both components also called previewFrame({ scroll: true }) and
LoadingOverlay visible={loading === true} without any test checking the
scrollable variant's own class name or the overlay's absence when not
loading. Added both directly, plus SheetPreview's per-kind cell
alignment/error classing (compared against the real vanilla-extract
recipe output rather than guessed class names) and its solid/pattern
fill-to-rgb colour resolution.
SlidesPreview's colorToCss multiplies each RGB channel by 255, but every
existing fixture used a channel value of 0 or 1, where multiplication and
division by 255 coincide. Added a fixture with distinct fractional values
per channel. Extracted the double-stroke vector's own React key strings
into doubleStrokeKeys, since a key never reaches rendered DOM output and
so was otherwise unobservable to any rendering-based test.
router.ts's own remaining gaps: the markdown-only styleId constants
(QUOTE_STYLE_ID/CODE_BLOCK_STYLE_ID) were never exercised through
normalizeContentForSource directly, and a wordprocessing-kind document
passed under a source that is neither markdown nor docx/odt was never
checked to pass through unrewritten. appendParagraphOf's own doc-format
branch turned out to be redundant -- DocEditor.appendParagraph is a plain
forward to its own body.appendParagraph, the exact call every other
format already uses -- so the branch is now gone rather than tested
around. Added abort-signal forwarding tests for metadata.read,
metadata.write, and pdf.inspect, each rejecting immediately given an
already-aborted signal.
… fixture ContentCellValue has no "text" variant -- the schema names the plain string kind "string", which tsc's Node-config program (the one that actually type-checks test files) caught immediately.
…ing.xml Neither the top-level nor the table-cell branch of normalizeDocxListKinds had any test at all -- both resolve a paragraph's opaque docx numId against real word/numbering.xml data via readDocxExtras, which no existing fixture in this file ever produced. Built via the public assembleTree/buildDocumentBytes tree pipeline rather than a hand-authored fixture, so the numbering definition router.ts resolves against is genuinely the shape a real docx carries. The writer currently always synthesises numId "1" as a bullet list regardless of the requested numId/format (#1273, filed separately), so both tests assert that real current output rather than the specific values requested.
…nd tooltip label formatBytes' own KB/MB thresholds were only ever tested comfortably above each boundary (2048, 3 * 1024 * 1024), leaving the exact-boundary comparisons free to become <= without any test noticing. Added tests at 1024 and 1024 * 1024 exactly. handleReopen's permission flow had two real branches with no test distinguishing them: skipping requestPermission once queryPermission already grants access, versus falling through to request it. The existing "granted" test used a handle whose requestPermission also resolved "granted", so forcing the request to always run changed nothing observable. Added a handle whose requestPermission would resolve "denied" if called, asserting it never is, plus assertions that both permission calls are actually made in "read" mode and that the denied-permission error names the record. reopenTooltipLabel is now a standalone exported function, extracted from the inline ternary building the Tooltip's label prop -- Mantine's Tooltip only mounts its floating content once genuinely open, which a render-only test can't drive, the identical reason __root.tsx's own colorSchemeTooltipLabel was already factored out the same way.
…read
Both useRecentFiles' orderBy("lastOpenedAt") and recordRecentFile's
eviction query had every existing fixture inserted in the same order as
its own lastOpenedAt value, so natural table iteration order happened to
coincide with the real sorted order regardless of whether orderBy
actually ran. Reordered both fixtures so insertion order and
lastOpenedAt genuinely disagree, which only a real orderBy resolves
correctly.
Extracted the stale-id narrowing in recordRecentFile into definedIds, a
plain exported function, so the id-undefined filter -- there only to
satisfy bulkDelete's number[] parameter against a real Dexie record's
possibly-absent id -- can be tested directly against a mixed array
rather than needing a live record no normal write path actually
produces.
router.ts's content.read had no test at all for odt, and
normalizeMarkdownParagraph's own bullet/ordered numId tagging (distinct
from the docx numbering-resolution path) was untested against a real
markdown list, unlike the confirmed-blocked docx equivalent
(#1273).
…cept memo
The Idle icon slot and the empty-hint paragraph were only ever checked
by filename/hint text, never by which icon actually rendered or whether
an empty hint paragraph exists at all -- both survived unnoticed because
rendering an absent icon or an empty {undefined} child produces
identical visible text either way. Checked the tabler-icon-file/-upload
class names directly, and counted the rendered <p> elements when no
file and no formatHint are present.
dropzoneAccept's own useMemo depends on the accept prop, but no test
ever rerendered the component with a changed accept value, so a
dependency array frozen to [] looked identical to the real one. Added a
rerender case that swaps the accept map and checks the normalised output
actually follows it.
multiple={false} on the underlying Dropzone had no assertion at all --
the mock component didn't even forward the prop. openFile's own
{ accept } argument was asserted only with accept left undefined in
every picker test, so the object literal mutating to {} changed
nothing observable; added a real accept value to that test's assertion.
…ic cells extractFonts.reset() runs unconditionally before the format check, but no test ever uploaded a second file after the first already produced a result -- removing the reset call changed nothing observable until a stale table could actually be left on screen. Added a second upload (recognised, then unrecognised) proving the first result is gone. The onError handler's own notifyError call, and its exact "Could not read fonts" message, had no assertion at all -- the rejection test only checked the table's absence. Mocked notify.ts and asserted the call. Every font fixture used the same bold/italic combination (false/true), so the "yes" and "no" table cells were each only ever exercised from one branch of their own ternary -- the other branch's own string literal was never reached at all. Added a second font with the opposite flags.
…cases formatLeaf's two truncation checks (the raw-string cap at 102 characters, the generic cap at 100) were only ever tested well above or well below each threshold, leaving both > comparisons free to become >= without any test noticing. Added exact-boundary fixtures for both the string and non-string (bigint) paths. kindSuffix's own early return for a non-plain-object value was never reached at all -- every existing array-item fixture was either a leaf or a plain object, never a nested array. Its kind !== string branch was similarly untested, since the one array-of-objects fixture always carried a genuine string kind. Added a nested-array item, an object item with a non-string kind, and an object item with no kind field.
…lob contents input.type was set but never checked by any test; the change listener's once: true option was passed but no test verified it was actually attached that way, and saveFile's Blob was only ever exercised indirectly through a mocked createObjectURL that ignores its own argument. Checked input.type directly, checked addEventListener's own call arguments for the change listener, and read the Blob object itself back off createObjectURL's mock call to assert its real size and type.
…tent
toContain("pages")/toContain("image")/toContain("1") loose substring
checks can pass even when the item-kind table renders nothing, since
the structure tree below separately renders its own "pages [0]" node
and formatVersion text containing the same substrings. Assert against
the mounted DOM's actual table rows and the precise pluralised count
string instead.
…M shape Add a not-loading counterpart to the existing loading-overlay assertion so an inverted loading check is genuinely covered both ways. Replace the ordered-list test's loose toContain checks with real querySelectorAll assertions on <ol>/<ul>/<li> counts and text, since substring matching cannot tell a genuine single <ol> apart from stray nesting or duplicate list rendering.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the Stryker typescript-checker crash for this package (router.ts and workers/** belong to tsconfig.worker.json, not the app tsconfig.json Stryker's checker was pointed at -- neither program alone covers everything the mutate glob touches) and adds real unit coverage across the previously-untested pure helpers, file-access adapters, the IndexedDB-backed recent-files store, every RPC-client-wrapping hook, every format-neutral preview component, and several route/UI files.
Progress so far, not yet complete:
?? "bin"fallback in the native save pickerautoCodeSplittingrewrites every route file's component behind a dynamic import regardless of whether anything goes through the generated route tree, so mounting any route'sRoute.options.componentdirectly in a test genuinely suspended on the first render. Gated off under vitest's own test mode, the same way this config already gatesbaseon the build/serve command -- confirmed the real production build still code-splits every route exactly as before.Remaining gap: src/rpc/router.ts's harder procedures (fonts.describe, odb.read, odm.render, non-markdown editor.save) are still untested; most of src/routes/** (recent.tsx, odb.tsx, inspect.tsx, package.tsx, odm.tsx, metadata.tsx, editors.tsx, convert.tsx, -Sidebar.tsx) has no unit tests yet. A first real Stryker baseline run is in progress (partial data so far: roughly 1100+/2870 mutants tested, ~50 survived) but has not completed within this session -- the package's real size (3000+ mutants across 81 mutated files) combined with heavy contention on the shared machine this ran on means a full run takes upward of an hour. Work continues on this branch.
No Stryker disable comments anywhere in the package.